/*
 * A tool to generate a Bitmap Font and a c_header_file which defined the name and indices of glyphs
 * author: VisualGMQ 2021-08-30
 * If your ttf don't have name table, is won't generate a c_header_file for you
 *
 * !!! WARN: due to the special name in name table, please check the c_header_file after generate!!!
 *
 * Compile Dependencies:
 * SDL2 SDL2_ttf Freetype2
 */
#include "SDL.h"
#include "SDL_ttf.h"
#include "ft2build.h"
#include FT_FREETYPE_H

#include <cstdlib>
#include <iostream>
#include <string>
#include <vector>
#include <sstream>
#include <fstream>
using namespace std;

enum ExitType {
    EXIT_PARAMETER_ERROR = 1,
    EXIT_SURFACE_ERROR = 2,
    EXIT_FREETYPE_LIB_ERROR = 3
};

int Pt;
int FontNumPerRow = 20;
TTF_Font* font = nullptr;
string Filename;
string CHeaderFilename;
FT_Library ftLibrary;

void ShowHelp() {
    cout << "Font BMP generator v1.0.0" << endl
         << "Usage:" << endl
         << "  BmpFonter <font_filename> <font_pt> <c_header_file_name> <font_num_per_row>" << endl
         << "  font_num_per_row = 20 by default" << endl;
}

void PushGlyphName(vector<string>& names, char* buf, int counter, int idx) {
    if (strlen(buf) != 0) {
        names.at(counter) = buf;
    } else {
        stringstream ss;
        ss << hex << idx;
        names.at(counter) = ss.str();
    }
}

void ParseCmdParams(int argc, char** argv) {
    if (argc != 4 && argc != 5) {
        ShowHelp();
        exit(EXIT_SUCCESS);
    }
    Pt = atoi(argv[2]);
    if (Pt <= 0) {
        cout << "Error: font_pt <= 0!" << endl;
        exit(EXIT_PARAMETER_ERROR);
    }
    CHeaderFilename = argv[3];
    Filename = argv[1];
    if (argc == 5) {
        FontNumPerRow = atoi(argv[4]);
        if (FontNumPerRow <= 0) {
            cout << "Error: font_num_per_row <= 0!" << endl;
            exit(EXIT_PARAMETER_ERROR);
        }
    }
}

int main(int argc, char** argv) {
    ParseCmdParams(argc, argv);
    SDL_Init(SDL_INIT_VIDEO);
    TTF_Init();
    auto error = FT_Init_FreeType(&ftLibrary);
    if (error) {
        cout << "Error: freetype lib init failed" << endl;
        return EXIT_FREETYPE_LIB_ERROR;
    }

    atexit([](){
            TTF_CloseFont(font);
            TTF_Quit();
            SDL_Quit();
            });

    font = TTF_OpenFont(Filename.c_str(), Pt);
    if (!font) {
        cout << "Error: " << Filename << " can't open" << endl;
        return EXIT_PARAMETER_ERROR;
    }

    FT_Face face;
    FT_New_Face(ftLibrary, Filename.c_str(), 0, &face);

    int glyphNum = 0;
    for (uint32_t i = 0; i <= 0xFFFF; i++) {
        if (TTF_GlyphIsProvided(font, (Uint16)i)) {
            glyphNum++;
        }
    }

    cout << "the font has " << glyphNum << " glyphs" << endl;

    int row = ceil(glyphNum / FontNumPerRow) + 1;
    SDL_Surface* surface = SDL_CreateRGBSurfaceWithFormat(0, FontNumPerRow * Pt, row * Pt, 24, SDL_PIXELFORMAT_RGB24);
    if (!surface) {
        cout << "Error: can't create surface: " << SDL_GetError() << endl;
        return EXIT_SURFACE_ERROR;
    }
    cout << "image size: " << surface->w << "x" << surface->h << endl;
    SDL_FillRect(surface, nullptr, SDL_MapRGB(surface->format, 0, 0, 0));

    vector<string> glyphNames(glyphNum);

    int counter = 0;
    uint16_t idx = 0;
    while (counter < glyphNum) {
        if (TTF_GlyphIsProvided(font, idx)) {
            SDL_Surface* sur = TTF_RenderGlyph_Solid(font, idx, {255, 255, 255, 255});
            if (!sur) {
                cout << "Error: create glyph failed!: " << SDL_GetError() << endl;
            } else {
                SDL_Rect rect = {(counter % FontNumPerRow) * sur->w, counter / FontNumPerRow * sur->h, sur->w, sur->h};
                SDL_BlitSurface(sur, nullptr, surface, &rect);
                SDL_FreeSurface(sur);
                if (FT_HAS_GLYPH_NAMES(face)) {
                    char buf[256] = {0};
                    FT_Get_Glyph_Name(face, counter + 1, buf, sizeof(buf) - 1);
                    PushGlyphName(glyphNames, buf, counter, idx);
                }
            }
            counter ++;
        }
        idx ++;
    }

    cout << "image generator OK" << endl;
    string saveFilename = "result" + to_string(Pt) + "x" + to_string(Pt) + ".bmp";

    SDL_SaveBMP(surface, saveFilename.c_str());
    cout << "image saved to " << saveFilename << endl;

    SDL_FreeSurface(surface);

    if (glyphNames.empty()) {
        cout << "Sorry, your TTF file don't have name glyph name! We can't generate a C header file for you." << endl;
    } else {
        ofstream file(CHeaderFilename);
        file << "_Pragma(\"once\")" << endl << endl
             << "#define SIGN_SHEET_ROW " << row << endl
             << "#define SIGN_SHEET_COL " << FontNumPerRow << endl << endl
             << "enum SignDef {" << endl;
        file << "    SIGN_" << glyphNames[0] << " = 0," << endl;
        for (int i = 1; i < glyphNames.size(); i++) {
            file << "    SIGN_" << glyphNames[i] << "," << endl;
        }
        file << "};";
        file.close();

        cout << "header file generated: " << CHeaderFilename << endl;
    }

    return 0;
}
